﻿using Gargoyle.Roton;
using Gargoyle.ZZT.File;
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;

namespace Gargoyle.ZZT
{
    public partial class Game : Roton.Environment
    {
        internal OOPState OOPState;

        internal bool BroadcastLabel(int sender, ByteString label)
        {
            return SendLabel(sender, Parameters.BroadcastTarget + ":" + label, false);
        }

        public void ClearFlag(ByteString flag)
        {
            int index = FlagIndex(flag);
            if (index >= 0)
            {
                Info.Flags[index] = new byte[0];
            }
        }

        internal byte ColorMatch(Tile tile)
        {
            Element element = Elements[tile.Element];
            int result;

            if (element.DefaultColor < 0xF0)
                result = (element.DefaultColor & 7);
            else if (element.DefaultColor == 0xFE)
                result = (((tile.Color >> 4) & 0x0F) + 8);
            else
                result = (tile.Color & 0x0F);

            return (byte)(result & 0xFF);
        }

        internal void ExecuteOOP(int index, ref Int16 instruction, ByteString name)
        {
            bool skip = false;
            Int16 previousInstruction;
            Thing thing;

            if (OOPState == null)
            {
                OOPState = new OOPState();
                thing = Things[index];
                OOPState.Index = index;
                OOPState.Thing = thing;
            }
            else
            {
                index = OOPState.Index;
                thing = OOPState.Thing;
                if (Display.ScrollLine.Length == 0)
                    skip = true;
                else
                {
                    if (Display.ScrollLine[0] == 0x2D)  // -
                    {
                        string helpFile = UpperCase(Display.ScrollLine);
                        helpFile = helpFile.Substring(1);
                        if (helpFile == "CON")
                        {
                            throw new NotImplementedException("Console invoked: this is not supported.");
                        }
                        else
                        {
                            ShowHelp(helpFile.Substring(1));
                        }
                    }
                    else
                    {
                        skip = !SendLabel(index, Display.ScrollLine, true);
                    }
                }

                OOPState.Message = new byte[] { };
            }

            if (!skip)
            {
                previousInstruction = instruction;
                OOPState.Moved = false;
                OOPState.Repeat = false;
                OOPState.Died = false;
                OOPState.Finished = false;
                OOPState.CommandsExecuted = 0;

                bool debugPrint = false;
                if (debugPrint)
                    System.Diagnostics.Debug.WriteLine("exec [" + index.ToString() + "]");

                do
                {
                    if (instruction < 0)
                        break;

                    OOPState.NextLine = true;
                    previousInstruction = instruction;

                    if (debugPrint)
                    {
                        Int16 debugInst = instruction;
                        System.Diagnostics.Debug.WriteLine("[" + index.ToString() + "] " + (string)ReadLine(thing, ref debugInst));
                    }

                    ReadByte(thing, ref instruction);
                    switch (OOPByte)
                    {
                        case 0x3A:  // :
                        case 0x27:  // '
                        case 0x40:  // @
                            ReadLine(thing, ref instruction);
                            break;
                        case 0x2F:  // /
                        case 0x3F:  // ?
                            Vector vector = new Vector(0, 0);
                            if (OOPByte == 0x2F)
                            {
                                OOPState.Repeat = true;
                            }
                            if (ReadDirection(thing, ref instruction, ref vector))
                            {
                                if (Parameters.MultiMovement)
                                {
                                    int count;
                                    ReadNumber(thing, ref instruction);
                                    count = OOPNumber;
                                    if (count < 0)
                                    {
                                        count = 1;
                                    }
                                    if (OOPByte == 0x3F)
                                    {
                                        count = -count;
                                    }
                                    thing.P2 = (byte)(count & 0xFF);
                                    thing.Vector = vector;
                                    OOPState.Repeat = false;
                                }
                                else
                                {
                                    if (vector.IsIdle)
                                    {
                                        OOPState.Repeat = false;
                                    }
                                    else
                                    {
                                        Location target = thing.Location.Add(vector);
                                        if (!GetElementAt(target).Floor)
                                        {
                                            Push(target, vector);
                                        }
                                        if (GetElementAt(target).Floor)
                                        {
                                            MoveThing(index, target);
                                            OOPState.Repeat = false;
                                        }
                                    }
                                }
                                ReadByte(thing, ref instruction);
                                if (OOPByte != 0x0D)
                                {
                                    instruction--;
                                }
                                OOPState.Moved = true;
                            }
                            else
                            {
                                RaiseOOPError(thing, "Bad direction");
                            }
                            break;
                        case 0x23:  // #
                            while (true)
                            {
                                OOPState.Resume = false;
                                OOPState.Executed = true;
                                ReadWord(thing, ref instruction);
                                if (OOPWord.Length == 0)
                                {
                                    break;
                                }

                                bool valid = false;
                                ByteString command = OOPWord;

                                for (int i = 0; i < Language.CommandFunctions.Count; i++)
                                {
                                    if (command == Language.CommandNames[i])
                                    {
                                        Language.CommandFunctions[i](this, OOPState, ref instruction);
                                        valid = true;
                                        break;
                                    }
                                }

                                if (!valid)
                                {
                                    if (!SendLabel(index, command, false))
                                    {
                                        if (Array.IndexOf<byte>(command, 0x3A) < 0) // :
                                        {
                                            RaiseOOPError(thing, "Bad command " + command);
                                        }
                                    }
                                    else
                                    {
                                        OOPState.NextLine = false;
                                    }
                                }

                                if (OOPState.Executed)
                                {
                                    OOPState.CommandsExecuted++;
                                    OOPState.Executed = false;
                                }
                                else
                                {
                                    OOPState.Resume = true;
                                }
                                if (OOPState.Resume)
                                {
                                    OOPState.Resume = false;
                                }
                                else
                                {
                                    if ((OOPState.NextLine) && (instruction >= 0))
                                    {
                                        ReadLine(thing, ref instruction);
                                    }
                                    break;
                                }
                            }
                            break;
                        default:
                            if (OOPByte == 0x0D)
                            {
                                if (OOPState.Message.Length > 0)
                                {
                                    OOPState.AppendMessage(new byte[]{ OOPByte });
                                }
                            }
                            else if (OOPByte == 0)
                            {
                                OOPState.Finished = true;
                            }
                            else
                            {
                                if (OOPState.Message.Length > 0)
                                {
                                    OOPState.AppendMessage(new byte[] { 0x0D });
                                }
                                OOPState.AppendMessage(new byte[]{ OOPByte });
                                OOPState.AppendMessage(ReadLine(thing, ref instruction));
                            }
                            break;
                    }

                } while (!OOPState.Finished && !OOPState.Moved &&
                    !OOPState.Repeat && !OOPState.Died &&
                    (OOPState.CommandsExecuted <= Parameters.MaxCommands));

                if (OOPState.Repeat)
                {
                    instruction = previousInstruction;
                }

                if (OOPByte == 0)
                {
                    instruction = -1;
                }

                if (OOPState.Message.Length > 0)
                {
                    if (OOPState.Message.CountOf(0x0D) >= Parameters.MessageLines)
                    {
                        if (thing.Code != null && thing.Code[0] == 0x40)
                        {
                            Int16 offset = 1;
                            name = ReadLine(thing, ref offset);
                            OOPState.Name = name;
                        }
                        SetScroll(name, OOPState.Message);
                    }
                    else
                    {
                        SetMessage(0xC8, OOPState.Message);
                    }
                }
            }

            if (!DisplayBusy)
            {
                if (OOPState.Died)
                {
                    Location location = thing.Location;
                    if (Parameters.PreKillObject)
                    {
                        HarmThing(index);
                    }
                    PlotTile(location, OOPState.Tile);
                }

                OOPState = null;
            }
        }

        internal bool FindLabel(int sender, ByteString label, ref int searchIndex, ref Int16 instruction, ByteString prefix)
        {
            bool success = false;
            ByteString target = new byte[0];
            byte splitter = 0x3A;
            int splitterOffset = Array.IndexOf<byte>(label, splitter); // :

            if (splitterOffset > 0)
            {
                ByteString[] sections = label.Split(splitter);
                target = sections[0];
                label = sections[1];
                success = FindTarget(sender, ref searchIndex, target);
            }
            else
            {
                if (searchIndex < sender)
                {
                    searchIndex = sender;
                    success = true;
                }
            }

            while (true)
            {
                if (success)
                {
                    if (label == Parameters.RestartLabel)
                    {
                        instruction = 0;
                    }
                    else
                    {
                        instruction = SearchOOP(searchIndex, prefix + label);
                        if (instruction < 0 && splitterOffset > 0)
                        {
                            success = FindTarget(sender, ref searchIndex, target);
                            continue;
                        }
                    }
                    success = (instruction >= 0);
                }
                break;
            }

            return success;
        }

        internal bool FindTarget(int source, ref int searchIndex, ByteString target)
        {
            bool result = false;
            bool success = false;

            searchIndex++;

            for (int i = 0; i < Language.TargetNames.Count; i++)
            {
                if (Language.TargetNames[i] == target)
                {
                    result = Language.TargetFunctions[i](this, source, ref searchIndex);
                    success = true;
                    break;
                }
            }

            while (!success)
            {
                if (searchIndex >= Things.Count)
                    break;

                Thing thing = Things[searchIndex];
                if (thing.Code != null)
                {
                    Int16 offset = 0;
                    ReadByte(thing, ref offset);
                    if (OOPByte == 0x40) // @
                    {
                        ReadWord(thing, ref offset);
                        if (OOPWord == target)
                        {
                            success = true;
                            result = true;
                        }
                    }
                }

                if (!success)
                    searchIndex++;
            }

            return result;
        }

        public int FlagIndex(ByteString flag)
        {
            for (int i = 0; i < Parameters.MaxFlags; i++)
            {
                if (Info.Flags[i] == flag)
                {
                    return i;
                }
            }
            return -1;
        }

        private bool IsNumericCharacter(byte c)
        {
            return ((c >= 0x30) && (c <= 0x39));
        }

        private bool IsAlphabeticCharacter(byte c)
        {
            return ((c >= 0x41) && (c <= 0x5A));
        }

        private bool IsValidOOPWordCharacter(byte c)
        {
            // alphabet
            if (IsAlphabeticCharacter(c))
                return true;

            // numbers
            if (IsNumericCharacter(c))
                return true;

            // symbols : and _
            if ((c == 0x5F) || (c == 0x3A))
                return true;

            return false;
        }

        internal byte OOPByte
        {
            get
            {
                return State.OOPByte;
            }
        }

        internal Int16 OOPNumber
        {
            get
            {
                unchecked
                {
                    return (Int16)(State.OOPNumber);
                }
            }
        }

        internal ByteString OOPWord
        {
            get
            {
                return State.OOPWord;
            }
        }

        internal void RaiseOOPError(Thing thing, string message)
        {
            SetMessage(0xC8, "ERR: " + message);
            thing.Instruction = -1;
        }

        internal void ReadByte(Thing thing, ref Int16 offset)
        {
            byte result;

            if (thing != null)
            {
                ByteString code = thing.Code;

                if ((code == null) || (offset >= thing.Length) || (offset < 0))
                {
                    result = 0;
                }
                else
                {
                    result = code[offset];
                    if (result != 0)
                    {
                        offset++;
                    }
                }
            }
            else
            {
                result = 0;
            }
            State.OOPByte = result;
        }

        internal bool ReadCondition(Thing thing, ref Int16 offset, ref bool result)
        {
            ReadWord(thing, ref offset);
            for (int i = 0; i < Language.ConditionFunctions.Count; i++)
            {
                if (Language.ConditionNames[i] == OOPWord)
                {
                    return (Language.ConditionFunctions[i](this, thing, ref offset, ref result));
                }
            }
            for (int i = 0; i < Parameters.MaxFlags; i++)
            {
                if (Info.Flags[i] == OOPWord)
                {
                    result = true;
                    return true;
                }
            }
            result = false;
            return true;
        }

        internal bool ReadDirection(Thing thing, ref Int16 offset, ref Vector vector)
        {
            ReadWord(thing, ref offset);
            for (int i = 0; i < Language.DirectionFunctions.Count; i++)
            {
                if (Language.DirectionNames[i] == OOPWord)
                {
                    return (Language.DirectionFunctions[i](this, thing, ref offset, ref vector));
                }
            }
            return false;
        }

        internal bool ReadItem(Thing thing, ref Int16 offset, ref ItemDelegate item)
        {
            ReadWord(thing, ref offset);
            ReadNumber(thing, ref offset);
            for (int i = 0; i < Language.ItemFunctions.Count; i++)
            {
                if (Language.ItemNames[i] == OOPWord)
                {
                    item = Language.ItemFunctions[i];
                    return true;
                }
            }
            return false;
        }

        internal bool ReadKind(Thing thing, ref Int16 offset, ref Tile kind)
        {
            int elementCount = Elements.Count;
            int colorCount = Parameters.Colors.Length;
            bool success = false;
            bool valid = false;

            kind = new Tile(0, 0);

            while (!success)
            {
                valid = false;
                ReadWord(thing, ref offset);

                if (kind.Color == 0)
                {
                    // this needs to be 1 as the game does not check color 0
                    for (byte i = 1; i < colorCount; i++)
                    {
                        if (UpperCase(Parameters.Colors[i]) == OOPWord)
                        {
                            kind.Color = (byte)(i + 8);
                            valid = true;
                            break;
                        }
                    }
                }

                if (!valid)
                {
                    for (byte i = 0; i < elementCount; i++)
                    {
                        if (Elements[i].OOPName == OOPWord)
                        {
                            kind.Element = i;
                            success = true;
                            valid = true;
                            break;
                        }
                    }
                }

                if (!valid)
                {
                    success = false;
                    break;
                }
            }

            return success;
        }

        internal ByteString ReadLine(Thing thing, ref Int16 offset)
        {
            List<byte> result = new List<byte>();
            
            if (offset >= 0)
            {
                do
                {
                    ReadByte(thing, ref offset);
                    if (OOPByte == 0x00 || OOPByte == 0x0D)
                    {
                        break;
                    }
                    result.Add(OOPByte);
                } while (true);
            }

            return result.ToArray();
        }

        internal void ReadNumber(Thing thing, ref Int16 offset)
        {
            byte character;
            Int16 result = 0;
            bool valid = false;

            do
            {
                ReadByte(thing, ref offset);
                character = State.OOPByte;
            } while (character == 0x20);

            valid = IsNumericCharacter(character);
            while (valid)
            {
                if (IsNumericCharacter(character))
                {
                    unchecked
                    {
                        result *= 10;
                        result += (Int16)(character - 0x30);
                    }
                }
                else
                {
                    break;
                }
                ReadByte(thing, ref offset);
                character = State.OOPByte;
            };

            if (offset > 0)
                offset--;

            if (!valid)
                State.OOPNumber = -1;
            else
                State.OOPNumber = result;
        }

        internal void ReadWord(Thing thing, ref Int16 offset)
        {
            List<byte> result = new List<byte>();
            byte character;

            do
            {
                ReadByte(thing, ref offset);
            } while (State.OOPByte == 0x20);

            character = UpperCase(State.OOPByte);
            if (!IsNumericCharacter(character))
            {
                do
                {
                    if (IsValidOOPWordCharacter(character))
                        result.Add(character);
                    else
                        break;

                    ReadByte(thing, ref offset);
                    character = UpperCase(State.OOPByte);
                } while (true);
            }

            if (offset > 0)
                offset--;

            State.OOPWord = result.ToArray();
        }

        internal Int16 SearchOOP(int index, ByteString target)
        {
            Thing thing = Things[index];
            int startOffset = 0;
            int searchOffset = 0;

            if (thing.Length <= 0)
                return -1;

            ByteString code = UpperCase(thing.Code);
            string codeString = (string)code;
            string targetString = (string)target;

            while (true)
            {
                startOffset = codeString.IndexOf(targetString, startOffset);
                if (startOffset >= 0)
                {
                    searchOffset = startOffset + target.Length;
                    if (searchOffset < code.Length && !(code[searchOffset] == 0x5F || IsAlphabeticCharacter(code[searchOffset])))
                    {
                        
                        return (Int16)startOffset;
                    }
                }
                else
                {
                    break;
                }
                startOffset++;
            }
            return -1;
        }

        internal bool SendLabel(int sender, ByteString label, bool force)
        {
            bool modifiedSelf = false;
            bool notSelf = false;
            int index = 0;
            Int16 instruction = 0;
            ByteString prefix = new byte[]{ 0x0D, 0x3A }; // <cr>:

            if (sender < 0)
            {
                sender = -sender;
                notSelf = true;
            }

            while (FindLabel(sender, label, ref index, ref instruction, prefix))
            {
                Thing thing = Things[index];
                bool locked;

                if (Parameters.MultiMovement)
                    locked = (thing.P3 != 0);
                else
                    locked = (thing.P2 != 0);

                if ((!locked) || (force) || ((sender == index) && (!notSelf)))
                {
                    if (index == sender)
                        modifiedSelf = true;

                    thing.Instruction = (Int16)instruction;
                }
            }

            return modifiedSelf;
        }

        public void SetFlag(ByteString flag)
        {
            int index = Parameters.MaxFlags - 1;
            if (FlagIndex(flag) == -1)
            {
                for (int i = 0; i < Parameters.MaxFlags; i++)
                {
                    if (Info.Flags[i] == null || Info.Flags[i].Length == 0)
                    {
                        index = i;
                        break;
                    }
                }
                Info.Flags[index] = flag;
            }
        }

        public void ToggleFlag(ByteString flag)
        {
            int index = FlagIndex(flag);

            if (index >= 0)
                ClearFlag(flag);
            else
                SetFlag(flag);
        }

        private byte UpperCase(byte c)
        {
            if ((c >= 0x61) && (c <= 0x7A))
                return (byte)(c - 0x20);
            return c;
        }

        private ByteString UpperCase(ByteString c)
        {
            ByteString result = c.Clone();
            for (int i = 0; i < result.Length; i++)
            {
                result[i] = UpperCase(result[i]);
            }
            return result;
        }
    }
}
